#include "HardwareSystem.h"
#include "WindowSystemMessages.h"
#include "HardwareSystemMessages.h"
#include "EngineMessages.h"
#include "EngineLog.h"

HardwareSystem::HardwareSystem(EnginePointer<SettingsManager>& settings, EnginePointer<IMessageQueue>& messages)
:IEngineSystem(settings, messages)
{
	windowSystem = NULL;
	pDevice = NULL;
	pSwapChain = NULL;
}

HardwareSystem::~HardwareSystem(void)
{
	windowSystem = NULL;
	pSwapChain = NULL;
	pDevice = NULL;
}

HRESULT HardwareSystem::Initialize(EnginePointer<SystemCollection>& systems)
{
	HRESULT hr;

	hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pDXGIFactory);
	if (FAILED(hr)) return hr;

	windowSystem = (EnginePointer<WindowSystem>)systems->getSystem(WINDOW_SYSTEM_GUID);
	if (windowSystem == NULL)
		return E_FAIL;

	hr = FindAdapter();
	if (FAILED(hr)) return hr;

	hr = InitializeDevice();
	if (FAILED(hr)) return hr;

	hr = InitializeSwapChain();
	if (FAILED(hr)) return hr;

	return S_OK;
}

void HardwareSystem::DeliverMessage(Message& m)
{
	if (strcmp(m.messageId, WINDOW_RESIZED_MESSAGE) == 0)
	{
		SendGraphicsResizeMessages();
	}
	else if (strcmp(m.messageId, RESIZE_GRAPHICS_MESSAGE) == 0)
	{
		ResizeGraphics();
	}
	else if (strcmp(m.messageId, GRAPHICS_SET_FULLSCREEN_MESSAGE) == 0)
	{
		BOOL fullscreen = (BOOL)m.uiParam;
		SetFullscreen(fullscreen);
	}
	else if (strcmp(m.messageId, GRAPHICS_TOGGLE_FULLSCREEN_MESSAGE) == 0)
	{
		ToggleFullscreen();
	}
	else if (strcmp(m.messageId, ENGINE_QUIT_MESSAGE) == 0)
	{
		SetFullscreen(FALSE);
	}
}

BOOL HardwareSystem::IsClean()
{
	BOOL fullscreen;
	pSwapChain->GetFullscreenState(&fullscreen, NULL);return !fullscreen;
}

GUID HardwareSystem::getSystemIdentifier()
{
	return HARDWARE_SYSTEM_GUID;
}

list<GUID> HardwareSystem::getDependencies()
{
	list<GUID> deps;
	deps.push_back(WINDOW_SYSTEM_GUID);
	return deps;
}

EnginePointer<ID3D10Device> HardwareSystem::GetDevice()
{
	return pDevice;
}

EnginePointer<IDXGISwapChain> HardwareSystem::GetSwapChain()
{
	return pSwapChain;
}

HRESULT HardwareSystem::FindAdapter()
{
	UINT nAdapter = 0;
	IDXGIAdapter1* adapter = NULL;
	driverType = D3D10_DRIVER_TYPE_HARDWARE;

	while (pDXGIFactory->EnumAdapters1(nAdapter,& adapter) != DXGI_ERROR_NOT_FOUND)
	{
		if (adapter)
		{
			DXGI_ADAPTER_DESC adaptDesc;
			if (SUCCEEDED(adapter->GetDesc(&adaptDesc)))
			{
				const bool isPerfHUD = wcscmp(adaptDesc.Description, L"NVIDIA PerfHUD") == 0;

				if(nAdapter == 0 || isPerfHUD) pAdapter = adapter;

				if(isPerfHUD) driverType = D3D10_DRIVER_TYPE_REFERENCE;
			}
		}
		++nAdapter;
	}

	HRESULT hr = pAdapter->EnumOutputs(0, &pOutput);
	if (FAILED(hr)) return hr;

	return S_OK;
}

HRESULT HardwareSystem::InitializeDevice()
{
	HRESULT hr;

#ifdef _DEBUG
	hr = D3D10CreateDevice(pAdapter, driverType, NULL, D3D10_CREATE_DEVICE_DEBUG, D3D10_SDK_VERSION, pDevice.innerAddress());
#else
	hr = D3D10CreateDevice(pAdapter, driverType, NULL, 0, D3D10_SDK_VERSION, pDevice.innerAddress());
#endif
	if (FAILED(hr)) return hr;

	return S_OK;
}

HRESULT HardwareSystem::InitializeSwapChain()
{
	HRESULT hr;

	bool fullscreen = settings->GetVariableAsBool("fullscreen");

	HWND hWnd = windowSystem->getHWnd();

	WINDOWINFO wInfo;
	GetWindowInfo(hWnd,& wInfo);

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
	sd.SampleDesc.Count = 1;
	sd.BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.BufferCount = 1;
	sd.OutputWindow = hWnd;
	sd.Windowed = !fullscreen;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	if (!fullscreen)
	{
		sd.BufferDesc.Width = wInfo.rcClient.right - wInfo.rcClient.left;
		sd.BufferDesc.Height = wInfo.rcClient.bottom - wInfo.rcClient.top;
	}

	hr = pDXGIFactory->CreateSwapChain(pDevice, &sd, pSwapChain.innerAddress());
	if (FAILED(hr)) return hr;

	SetFullscreen(fullscreen);

	return S_OK;
}

void HardwareSystem::SendGraphicsResizeMessages()
{
	Message m;
	m.messageId = GRAPHICS_RESIZING_MESSAGE;
	m.uiParam = 0;
	messages->Enqueue(m);
	m.messageId = RESIZE_GRAPHICS_MESSAGE;
	m.uiParam = 0;
	messages->Enqueue(m);
}

void HardwareSystem::ResizeGraphics()
{
	pSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
	SendGraphicsResizedMessage();
}

void HardwareSystem::SendGraphicsResizedMessage()
{
	Message m;
	m.messageId = GRAPHICS_RESIZED_MESSAGE;
	m.uiParam = 0;
	messages->Enqueue(m);
}

void HardwareSystem::SetFullscreen(BOOL fullscreen)
{
	pSwapChain->SetFullscreenState(fullscreen, NULL);
	pSwapChain->GetContainingOutput(&pOutput);
}

void HardwareSystem::ToggleFullscreen()
{
	BOOL fullscreen;
	IDXGIOutput* fullscreenOutput;
	pSwapChain->GetFullscreenState(&fullscreen, &fullscreenOutput);
	SetFullscreen(!fullscreen);
}